/*
 * COPYRIGHT:	2010 Christopher Smith, licenced under the GNU GPL v2; see COPYING in the top level directory
 * PROJECT:		sdedit
 * FILE:		auto_array.cpp
 * PURPOSE:		automatic (C++ semantics) reference counting container and reference classes
 */

class auto_refctd_std_base
{
	volatile long refs;

protected:
	auto_refctd_std_base() :
		refs(0)
	{}
	virtual ~auto_refctd_std_base()
	{}
	virtual void no_refs() //called when refs reach zero
	{}

public:
	void inc_ref()
	{
		InterlockedIncrement(&refs);
	}
	void dec_ref()
	{
		if(!InterlockedDecrement(&refs))
		{
			no_refs();
			if(!refs)
				delete this;
		}
	}
	void dec_ref_unsafe() //don't delete
	{
		InterlockedDecrement(&refs);
	}
};

//takes an arbitrary type and wraps it in a refct'd indirection
template <class T> class auto_refctd_container : auto_refctd_std_base, auto_ptr<T>
{};

template <class T> class auto_refct
{
	T *ptr;

	void free_if()
	{
		if(ptr)
			ptr->dec_ref();

		ptr = 0;
	}

public:
	auto_refct() :
	  ptr(0)
	{}
	auto_refct(T *x) :
		ptr(0)
	{
		if(x)
		{
			x->inc_ref();
			ptr = x;
		}
	}
	auto_refct(auto_refct const &x) :
		ptr(0)
	{
		if(x.ptr)
		{
			x.ptr->inc_ref();
			ptr = x.ptr;
		}
	}
	template <class U> auto_refct(auto_take<U> src) :
		ptr(src.ptr)
	{}
	void reset()
	{
		free_if();
	}
	void set(T *x)
	{
		free_if();
		if(x)
		{
			x->inc_ref();
			ptr = x;
		}
	}
	T *get() const
	{
		return ptr;
	}
	auto_take<T> release()
	{
		auto_take<T> ret(ptr);
		ptr = 0;
		return ret;
	}
	T *release_unsafe()
	{
		T *ret = ptr;
		free_if();
		return ret;
	}
	operator T *const &() const
	{
		return ptr;
	}
	T *const &operator->() const
	{
		return ptr;
	}
	auto_refct &operator =(auto_refct const &x)
	{
		set(x.ptr);
		return *this;
	}
	template <class U> auto_refct &operator =(auto_refct<U> const &x)
	{
		set(x.get());
		return *this;
	}
	auto_refct &operator =(T *x)
	{
		set(x);
		return *this;
	}
	~auto_refct()
	{
		free_if();
	}
};